﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Data.SqlClient;

using System.Data;
using System.Data.Common;
using log4net;
using Pampa.Common.Utils;

using Pampa.Grid.Core;
using Pampa.Grid.Core.Filters;

namespace Pampa.Grid.Data
{
    public abstract class AbstractQueryBuilder
    {
        protected readonly ILog logger;

        protected SortedList<string, object> parameters;

        protected string source;
        protected List<string> joins;

        protected List<string> fields;
        protected List<string> criteria;
        protected List<KeyValuePair<string, OrderDirection>> orderBy;
        protected SortedList<string, object> values;
        protected MultiSet<string> parameterNames;

        protected AbstractQueryBuilder()
        {
            parameters = new SortedList<string, object>();

            fields = new List<string>();
            joins = new List<string>();
            criteria = new List<string>();
            orderBy = new List<KeyValuePair<string, OrderDirection>>();
            values = new SortedList<string, object>();
            parameterNames = new MultiSet<string>();

            logger = LogManager.GetLogger(this.GetType());
        }

        #region Public Functions

        public string Source
        {
            get { return source; }
            set { source = value; }
        }

        public void AddJoin(string join)
        {
            joins.Add(join);
        }

        public void AddCriteria(Filter filter)
        {
            criteria.Add(GetScript(filter));
        }

        public void AddOrder(string field, OrderDirection direction)
        {
            KeyValuePair<string, OrderDirection> order = new KeyValuePair<string, OrderDirection>(field, direction);
            orderBy.Add(order);
        }

        public void AddValue(string field, Object value)
        {
            values.Add(field, value);
        }

        #endregion

        #region Query contruction methods

        protected virtual string GetAlias(string name)
        {
            if (name == null)
                return null;
            else if (name.Length >= 3)
                return name.Substring(0, 3);
            else
                return name;
        }

        protected virtual string GetQueryString(int? top)
        {
            StringBuilder sb = new StringBuilder();

            // SELECT
            sb.AppendLine(" SELECT distinct ");
            sb.AppendLine(GetSelectClause(top));

            // FROM
            sb.AppendLine(" FROM ");
            AppendSourceAndJoins(sb);

            // WHERE
            if (this.criteria.Count > 0)
            {
                sb.AppendLine(" WHERE ");
                AppendCriterias(sb);
            }

            // ORDER BY
            sb.AppendLine(" ORDER BY ");
            AppendOrders(sb);

            return sb.ToString();
        }

        protected virtual string GetCountQueryString()
        {
            StringBuilder sb = new StringBuilder();

            // SELECT
            sb.AppendLine(" SELECT ");
            sb.AppendLine(" count(" + GetSelectClause(null) + ") ");

            // FROM
            sb.AppendLine(" FROM ");
            AppendSourceAndJoins(sb);

            // WHERE
            if (this.criteria.Count > 0)
            {
                sb.AppendLine(" WHERE ");
                AppendCriterias(sb);
            }

            return sb.ToString();
        }

        /// <summary>
        /// Esta funcion sirve para resolver el conflicto de parametros con el mismo nombre 
        /// provenientes de condiciones sobre el mismo campo
        /// </summary>
        /// <param name="paramName"></param>
        /// <returns></returns>
        protected string GetParameterName(string paramName)
        {
            int count = parameterNames.Add(paramName);
            return paramName + count.ToString();
        }

        protected void AppendSourceAndJoins(StringBuilder sb)
        {
            sb.AppendLine(" " + this.Source + " ");
            sb.AppendLine(GetAlias(this.Source));

            foreach (string join in this.joins)
            {
                sb.AppendLine(join);
            }
        }

        protected void AppendCriterias(StringBuilder sb)
        {
            bool firstCondition = true;
            foreach (string condition in this.criteria)
            {
                sb.AppendLine(((!firstCondition) ? "AND " : " ") + condition);
                firstCondition = false;
            }
        }

        protected void AppendOrders(StringBuilder sb)
        {
            bool firstOrder = true;
            string direction;

            foreach (KeyValuePair<string, OrderDirection> order in this.orderBy)
            {
                if (order.Value == OrderDirection.Ascending)
                    direction = " ASC";
                else
                    direction = " DESC";

                sb.AppendLine(((!firstOrder) ? ", " : " ") + GetAlias(Source) + "." + order.Key + direction);

                firstOrder = false;
            }
        }

        #endregion

        #region Filters Function

        private string GetScript(Filter filter)
        {
            if (filter is GroupFilter)
                return GetScript((GroupFilter)filter);
            else
                return GetScript((SimpleFilter)filter);
        }

        protected virtual string GetScript(GroupFilter filter)
        {
            StringBuilder sb = new StringBuilder();
            bool firstFilter = true;

            sb.Append("(");

            foreach (Filter childFilter in filter.InnerFilters)
            {
                sb.Append(((!firstFilter) ? (" " + filter.GetFilterType() + " ") : "") + GetScript(childFilter));
                firstFilter = false;
            }

            sb.Append(")");

            return sb.ToString();
        }

        protected virtual string GetScript(SimpleFilter filter)
        {
            string paramName = GetParameterWithMark(GetParameterName(filter.Field));

            string comp = filter.OperationScript;
            object value = filter.ValueScript;

            parameters.Add(paramName, value);

            return "(" + GetAlias(Source) + "." + filter.Field + " " + comp + " " + paramName + ")";
        }

        #endregion

        #region Abstract Functions

        protected abstract string GetParameterWithMark(string paramName);

        protected abstract string GetSelectClause(int? top);
        
        #endregion
    }
}
